import { nextTick } from "../nextTick"
import Dep, { popTarget, pushTarget } from "./dep"

let id = 0

/**
 * fn 本质就是 vm._update(vm._render())
 * renderWatcher 标记是不是渲染watcher
 */
class Watcher {
  constructor(vm, exprOrFn, options, cb) {
    this.id = id++
    this.renderWatcher = options
    this.deps = []
    this.depIds = new Set()

    if (typeof exprOrFn === "string") {
      // watch用到的属性
      this.getter = function () {
        return vm[exprOrFn]
      }
    } else {
      /**
       * exprOrFn
       * 渲染watcher和计算watcher本质就是 vm._update(vm._render())
       * watch watcher 是用户传进来来的函数 ((newValue, oldValue) => {})
       */
      this.getter = exprOrFn
    }

    // watch用到的属性
    this.cb = cb
    this.user = options.user

    // 计算属性缓存值
    this.vm = vm
    this.lazy = options.lazy
    this.dirty = this.lazy

    // 这是的this.value 为watch 的旧值
    this.value = this.lazy ? null : this.get() // 初始渲染一次
  }

  /**
   * 每个组件都有自己的watcher
   * Dep.target 会不断变化替换，然后会 dep.depend() 方法上收集好watcher
   * js是单线程，不会有问题
   * this.getter 如果是计算watcher那就是一个computed里面的getter
   */
  get() {
    pushTarget(this)
    let result = this.getter.call(this.vm)
    popTarget()
    return result
  }

  // 计算属性用, 计算属性的结果放到watcher的value上
  // 这是的this.getter是computed里的getter
  // 这是的this.value 是computed 的新值
  evaluate() {
    this.value = this.get()
    this.dirty = false
  }
  // 让计算属性依赖的值的dep去收集当前的watcher
  depend() {
    let i = this.deps.length
    while (i--) {
      this.deps[i].depend()
    }
  }

  // this.lazy 表示这是一个计算watcher，this.dirty 表示这是个脏值 ，不更新视图
  // 如果是个 计算watcher dep.notify() 走到这里就结束了，仅仅会把这个标识改了，
  // 然后在 queueWatcher 里面 渲染watcher会重新走到computed的getter
  // 又因为 watcher.dirty 为true，所以会重新走 watcher.evaluate() ，这样实现了依赖更新，computed 自动更新
  update() {
    if (this.lazy) {
      this.dirty = true
    } else {
      queueWatcher(this)
    }
  }

  addDep(dep) {
    let id = dep.id
    if (!this.depIds.has(id)) {
      this.depIds.add(id)
      this.deps.push(dep)
      dep.addSub(this)
    }
  }

  run() {
    let newValue = this.get()
    let oldValue = this.value
    if (this.user) {
      this.cb.call(this.vm, newValue, oldValue)
    }
  }
}

/**
 * 将watcher放到队列中执行
 */
let queue = []
let has = {}
let pending = false
function queueWatcher(watcher) {
  let id = watcher.id
  if (!has[id]) {
    queue.push(watcher)
    has[id] = true
    if (!pending) {
      nextTick(flushQueueWatcher)
      pending = true
    }
  }
}

// 刷新队列并执行
function flushQueueWatcher() {
  let flushQueue = queue.slice(0)
  queue = []
  has = {}
  pending = false
  flushQueue.forEach(watcher => watcher.run())
}

export default Watcher
